# InsertCommandBuilder.java

    package org.codefilarete.stalactite.sql.order;

    import java.sql.PreparedStatement;
    import java.util.stream.Collectors;

    import org.codefilarete.stalactite.query.builder.SQLBuilder;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.statement.ColumnParameterizedSQL;
    import org.codefilarete.stalactite.sql.statement.DMLGenerator;
    import org.codefilarete.stalactite.sql.statement.PreparedSQL;
    import org.codefilarete.stalactite.sql.statement.SQLStatement;
    import org.codefilarete.stalactite.sql.statement.binder.ParameterBinder;
    import org.codefilarete.stalactite.sql.statement.binder.PreparedStatementWriter;
    import org.codefilarete.stalactite.sql.statement.binder.PreparedStatementWriterIndex;
    import org.codefilarete.tool.collection.KeepOrderSet;

    /**
     * A SQL builder for {@link Insert} objects
     * Can hardly be mutualized with {@link DMLGenerator} because this class provides
     * {@link InsertStatement} which let caller reuse it by setting several time its value through {@link InsertStatement#setValue(Column, Object)}
     *
     * @author Guillaume Mary
     */
    public class InsertCommandBuilder<T extends Table<T>> implements SQLBuilder {

    	private final Insert<T> insert;
    	private final Dialect dialect;

    	public InsertCommandBuilder(Insert<T> insert, Dialect dialect) {
    		this.insert = insert;
    		this.dialect = dialect;
    	}

    	@Override
    	public String toSQL() {
    		KeepOrderSet<? extends Column<T, ?>> columns = insert.getRow().stream().map(ColumnVariable::getColumn).collect(Collectors.toCollection(KeepOrderSet::new));
    		ColumnParameterizedSQL<T> columnParameterizedSQL = dialect.getDmlGenerator().buildInsert(columns);
    		return columnParameterizedSQL.getSQL();
    	}

    	public InsertStatement<T> toStatement() {
    		KeepOrderSet<? extends Column<T, ?>> columns = insert.getRow().stream().map(ColumnVariable::getColumn).collect(Collectors.toCollection(KeepOrderSet::new));
    		ColumnParameterizedSQL<T> columnParameterizedSQL = dialect.getDmlGenerator().buildInsert(columns);
    		PreparedStatementWriterIndex<Column<T, ?>, PreparedStatementWriter<?>> parameterBinderProvider = columnParameterizedSQL.getParameterBinderProvider();
    		InsertStatement<T> result = new InsertStatement<>(parameterBinderProvider, columnParameterizedSQL);
    		insert.getRow().forEach(c -> result.setValue(c.getColumn(), c.getValue()));
    		return result;
    	}

    	/**
    	 * A specialized version of {@link PreparedSQL} dedicated to {@link Insert} so one can set column values of the insert clause
    	 * through {@link #setValue(Column, Object)}.
    	 * Here is a usage example:
    	 * <pre>{@code
    	 * InsertStatement<T> insertStatement = new InsertCommandBuilder<>(this).toStatement(dialect.getColumnBinderRegistry());
    	 * try (WriteOperation<Integer> writeOperation = dialect.getWriteOperationFactory().createInstance(insertStatement, connectionProvider)) {
    	 *     writeOperation.setValues(insertStatement.getValues());
    	 *     writeOperation.execute();
    	 * }
    	 * // eventually change some values and re-execute it
    	 * insertStatement.setValue(..);
    	 * }</pre>
    	 */
    	public static class InsertStatement<T extends Table<T>> extends SQLStatement<Column<T, ?>> {

    		private final ColumnParameterizedSQL<T> columnParameterizedSQL;

    		/**
    		 * Single constructor, not expected to be used elsewhere than {@link UpdateCommandBuilder}.
    		 *
    		 * @param parameterBinderProvider binder for prepared statement values
    		 * @param columnParameterizedSQL
    		 */
    		private InsertStatement(PreparedStatementWriterIndex<Column<T, ?>, PreparedStatementWriter<?>> parameterBinderProvider, ColumnParameterizedSQL<T> columnParameterizedSQL) {
    			super(parameterBinderProvider);
    			this.columnParameterizedSQL = columnParameterizedSQL;
    		}

    		@Override
    		public String getSQL() {
    			return columnParameterizedSQL.getSQL();
    		}

    		@Override
    		protected void doApplyValue(Column<T, ?> key, Object value, PreparedStatement statement) {
    			PreparedStatementWriter<Object> paramBinder = getParameterBinder(key);
    			if (paramBinder == null) {
    				throw new BindingException("Can't find a " + ParameterBinder.class.getName() + " for column " + key.getAbsoluteName() + " of value " + value
    						+ " on sql : " + getSQL());
    			}
    			doApplyValue(columnParameterizedSQL.getColumnIndexes().get(key)[0], value, paramBinder, statement);
    		}
    	}
    }